package com.google.code.mochaccino.framework.crypto.x509;

import com.google.code.mochaccino.framework.crypto.EncryptionException;
import com.google.code.mochaccino.framework.crypto.keystore.KeyStoreManager;
import java.security.KeyStore;
import java.security.Provider;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

/**
 * A X509 Trust Manager Implementation that aggregates multiple X509 Trust Manager into
 * a single one. Albeit odd as a concept, it allows to on-the-fly changes the accepted
 * trust managers at runtime.
 */
public final class X509TrustManagerAggregator implements X509TrustManager {

	/** Trust Managers */
	private List<X509TrustManager> managers = Collections.synchronizedList( new ArrayList<X509TrustManager>() );

	/** Constructor */
	public X509TrustManagerAggregator() {
	}

	/** Add trust manager */
	public void addTrustManager( String algo, KeyStoreManager keyStore ) throws EncryptionException {
		addTrustManager( algo, null, keyStore.getKeyStore() );
	}

	/** Add trust manager */
	public void addTrustManager( String algo, Provider provider, KeyStore keyStore ) throws EncryptionException {
		try {
			TrustManagerFactory keyManagerFactory = provider != null ? TrustManagerFactory.getInstance( algo, provider.getName() ) : TrustManagerFactory.getInstance( algo );
			keyManagerFactory.init( keyStore );
			for ( TrustManager manager : keyManagerFactory.getTrustManagers() ) {
				if ( manager instanceof X509KeyManager ) {
					addTrustManager( (X509TrustManager) manager );
				}
			}
		} catch ( Exception e ) {
			throw new EncryptionException( e );
		}
	}

	/** Add trust manager */
	public void addTrustManager( X509TrustManager trustManager ) {
		managers.add( trustManager );
	}

	/** Add trust manager */
	public void addTrustManager( String algo, KeyStore keyStore ) throws EncryptionException {
		addTrustManager( algo, null, keyStore );
	}

	/** Implementation of the aggregation */
	public void checkClientTrusted( X509Certificate[] chain, String authType ) throws CertificateException {
		CertificateException lastException = null;
		for ( X509TrustManager manager : managers ) {
			try {
				manager.checkClientTrusted( chain, authType );
				return;
			} catch ( CertificateException e ) {
				lastException = e;
			}
		}

		throw lastException;
	}

	/** Implementation of the aggregation */
	public void checkServerTrusted( X509Certificate[] chain, String authType ) throws CertificateException {
		CertificateException lastException = null;
		for ( X509TrustManager manager : managers ) {
			try {
				manager.checkServerTrusted( chain, authType );
				return;
			} catch ( CertificateException e ) {
				lastException = e;
			}
		}

		throw lastException;
	}

	/** Implementation of the aggregation */
	public X509Certificate[] getAcceptedIssuers() {
		Set<X509Certificate> acceptedIssuers = new HashSet<X509Certificate>();

		for ( X509TrustManager manager : managers ) {
			for ( X509Certificate cert : manager.getAcceptedIssuers() ) {
				acceptedIssuers.add( cert );
			}
		}

		return acceptedIssuers.toArray( new X509Certificate[acceptedIssuers.size()] );
	}
}
